{# Chat macros: the transcript's one set of bubbles. A live turn and a history replay draw the same markup: ``user_bubble`` for what was asked, ``streaming_bubble`` while the answer arrives (the script fills its body), ``assistant_bubble`` for a stored, settled message (markdown, the tool trail, the footer). Every hook the scripts read is a data attribute or an id here, never framework scope, which is what lets one macro serve both a history render and the swap at the end of a live turn. #} {% from "components/macros/icons.html" import icon %} {% from "components/macros/layout.html" import avatar, copy_icon %} {# The chat's buttons: outlined and quiet, so the conversation stays louder than the controls. ``tone`` primary is teal; ``size`` sm or xs. #} {% macro button_classes(tone="default", size="sm") -%} {% set tones = {"default": "border-aegis-border text-aegis-muted hover:border-aegis-muted hover:text-aegis-text hover:bg-aegis-border/20", "primary": "btn-primary"} %} btn btn-outline {{ {"sm": "btn-sm", "xs": "btn-xs"}[size] }} {{ tones.get(tone, tones["default"]) }} {%- endmacro %} {# What a voice control is doing, drawn from its data-state (voice.js only sets it): the resting icon (the caller) while idle; recording, a pulsing dot (the mic only, ``record=True``); thinking, a spinner while audio is heard or fetched; speaking, an equalizer while it plays. The control carries ``group relative``; one set of visuals for the mic and every speaker. #} {% macro voice_states(size="w-4 h-4", record=False, work=False) %} {{ caller() }} {% if record %} {% endif %} {{ icon("spinner", "hidden group-data-[state=thinking]:block " ~ size ~ " animate-spin", 1.5, 'data-mic-visual="thinking"') }} {# ``work``: the agent pulling data mid-call - not the spinner, which is connecting or transcribing. #} {% if work %} {% endif %} {% endmacro %} {# A card under a reply, loaded in place by its own route. #} {% macro card_loader(url, what) %}
Attached: {{ attachment_names|join(", ") }}
{% endif %}